package cn.cai.pethome.user.service.impl;


import cn.cai.pethome.base.util.AjaxResult;
import cn.cai.pethome.base.util.MD5Utils;
import cn.cai.pethome.base.util.SmsUtil;
import cn.cai.pethome.base.util.StrUtils;
import cn.cai.pethome.logininfo.domain.Logininfo;
import cn.cai.pethome.logininfo.service.ILogininfoService;
import cn.cai.pethome.user.domain.User;
import cn.cai.pethome.user.dto.UserDto;
import cn.cai.pethome.user.mapper.UserMapper;
import cn.cai.pethome.user.service.IUserService;
import cn.cai.pethome.base.service.impl.BaseServiceImpl;

import cn.hutool.core.lang.UUID;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiaocai
 * @since 2022-08-31
 */
@Slf4j
@Service
public class UserServiceImpl extends BaseServiceImpl<User> implements IUserService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SmsUtil smsUtil;
    private static String smsCode;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ILogininfoService logininfoService;


    /**
     * 获取短信验证码
     * @param userDto
     * @return
     */
    @Transactional
    @Override
    public AjaxResult sendMobileCode(UserDto userDto) {
        //1.校验参数、图片验证码
        Assert.isTrue(StringUtils.hasText(userDto.getPhone()), "手机号不能为空！");
        //手机号格式
        Pattern pattern = Pattern.compile("^1[3-9]{1}[0-9]{9}$");
        Assert.isTrue(pattern.matcher(userDto.getPhone()).find(),"您输入的电话格式不正确");

        //图形验证码及其key(32位字符串)都不能为空
        Assert.isTrue(StringUtils.hasText(userDto.getImageCode()),"图形验证码");
        Assert.isTrue(StringUtils.hasText(userDto.getImageCodeKey()),"图形验证码的key不能为空");

        //检查图形是否正确：先使用key从redis中获取一个验证码
        Assert.isTrue(redisTemplate.hasKey(userDto.getImageCodeKey()),"图形验证码已失效");//；判断redis中是否存在

        System.out.println( redisTemplate.hasKey(userDto.getImageCodeKey())+ "cff");

        Object imageKey = redisTemplate.opsForValue().get(userDto.getImageCodeKey());//获取图形验证码
        Assert.isTrue(userDto.getImageCode().equals(imageKey),"图形验证码错误！");

        //2.校验手机号是否已经注册过：通过用户提交的手机号来查询t_user表，能查询到表示已经注册过，就不允许注册了
        User user = userMapper.findusername(userDto.getPhone());
        Assert.isTrue(user==null,"用户名已存在！！！");

        /**
         * 提升思维
         * 【重要】3.校验一分钟内不能多次获取验证码，两个方案
         *  1）两次执行到这里的时间差：
         *      这里能够获取本次的时间  System.currentTimeMillis()
         *      如何获取上一次的时间？ 每次保存的时候：手机号 -> 短信验证码#当前时间
         *      第二次先获取redis里面的value，再以#拆分字符串，取第二段就是上次获取的时间了
         *      if(本次的时间戳 - 上一次的时间戳 < 60*1000)  这就表示两次获取的时间是在一分钟之内，给出提示，就不再发短信了。
         *  2）【推荐】短信验证码设置的过期时间是300秒
         *      我们可以直接获取这个短信验证码的剩余过期时间，如果大于240，就表示在一分钟之内
         */
        if (redisTemplate.hasKey(userDto.getPhone())){
            //redis中存在这个手机对应的验证码  就需要获取剩余过期时间进行判断   TimeUnit.SECONDS=>秒数
            Long expireSeconds = redisTemplate.getExpire(userDto.getPhone(), TimeUnit.SECONDS);
            if (expireSeconds > 240){
                //剩余过期时间如果大于240，就表示在一分钟之内
                return AjaxResult.error("请稍后再试！！！");
            }else {
                //超过了1分钟，就直接使用上次的验证码，也可以重新生成一个验证码
                smsCode = (String) redisTemplate.opsForValue().get(userDto.getPhone());
                System.out.println(smsCode + "smsCode");
            }

        }else {
            //生成新的验证码
            smsCode = StrUtils.getRandomString(6);
        }
        //4.以手机号为key，存储到Redis，300秒有效【这里的过期时间300秒也可以配置到yml中，避免硬编码问题】
        redisTemplate.opsForValue().set(userDto.getPhone(),smsCode,300,TimeUnit.SECONDS);

        //5.调用三方接口发送短信：java发送HTTP请求 发短信、发邮件都是比较耗时的操作，建议使用异步编程
        //异步提价
        new Thread(new Runnable() {
            @Override
            public void run() {
                String text = "您的短信验证码是：" + smsCode + "，请在5分钟内使用，切记泄露！";
                //smsUtil.sendSms(userDto.getPhone(),text);最开发的时候开启
                log.info("正在发送短信：{}",text);
            }
        }).start();

        return AjaxResult.me();
    }



    /**
     * 手机短信验证码注册
     * @param userDto
     * @return
     */
    @Transactional
    @Override
    public void phoneRegister(UserDto userDto) {
        //1.校验参数、图片验证码
        Assert.isTrue(StringUtils.hasText(userDto.getPhone()), "手机号不能为空！");
        //手机号格式
        Pattern pattern = Pattern.compile("^1[3-9]{1}[0-9]{9}$");
        Assert.isTrue(pattern.matcher(userDto.getPhone()).find(),"您输入的电话格式不正确");

        //图形验证码及其key都不能为空
        Assert.isTrue(StringUtils.hasText(userDto.getImageCode()),"图形验证码");
        Assert.isTrue(StringUtils.hasText(userDto.getImageCodeKey()),"图形验证码的key不能为空");

        //检查图形是否正确：先使用key从redis中获取一个验证码
        Assert.isTrue(redisTemplate.hasKey(userDto.getImageCodeKey()),"图形验证码已失效");
        Object imageKey = redisTemplate.opsForValue().get(userDto.getImageCodeKey());//获取图形验证码
        Assert.isTrue(userDto.getImageCode().equals(imageKey),"图形验证码错误！");

        //3.校验两次密码一致
        Assert.isTrue(userDto.getPassword().equals(userDto.getConfirmPassword()),"两次密码必须一致");
        //2.校验手机号是否已经注册过：通过用户提交的手机号来查询t_user表，能查询到表示已经注册过，就不允许注册了
        User findusername = userMapper.findusername(userDto.getPhone());
        Assert.isTrue(findusername==null,"用户名已存在！！！");


        //5.保存t_loginInfo 登录账号信息 返回主键
        Logininfo logininfo = new Logininfo();
        logininfo.setUsername(userDto.getPhone());
        logininfo.setPhone(userDto.getPhone());
        logininfo.setEmail(userDto.getPhone() + "@qq.com");
        //盐值：MD5加密的时候洒了一把盐，目的是增加密码的安全性
        //UUID：生成绝对唯一的字符串
        String salt = UUID.randomUUID().toString();//盐值
        logininfo.setSalt(salt);
        //MD5加密,拼接UUID，UUID生成绝对唯一的字符串，后面再来写
        String md5password = MD5Utils.encrypByMd5(userDto.getPassword() + salt);
        logininfo.setPassword(md5password);
        logininfo.setType(1);
        logininfoService.add(logininfo);
        //6.保存数据到t_user
        User user = new User();
        user.setUsername(userDto.getPhone());
        user.setEmail(userDto.getPhone() + "@qq.com");
        user.setPhone(userDto.getPhone());
        user.setSalt(salt);
        user.setPassword(md5password);
        user.setLogininfo_id(logininfo.getId());//返回主键才能拿到的logininfo表的id
        userMapper.add(user);

    }

}
